package com.ramnight.unify.kit;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import lombok.Builder;
import lombok.Data;

@SuppressWarnings({ "rawtypes", "unchecked" })
public class UnifyPicker {
	private static UnifyPicker instance = new UnifyPicker();
	private static Map<String, PathNode> pathNodeCache = Maps.newConcurrentMap();

	public static UnifyPicker getInstance() {
		return instance;
	}

	private enum NodeType {
		FIELD, INDEX, STAR, FUNC;
	}

	@Data
	@Builder
	private static class PathNode {
		private NodeType type;
		private String field;
		private int index;

		private PathNode next;
		private boolean isHead;

		public PathNode next() {
			return next;
		}

		public boolean hasNext() {
			return next != null;
		}

		public PathNode add(PathNode node) {
			this.next = node;
			return next;
		}
	}

	@Data
	private static class Cursor {
		private PathNode node;
		private Object target;
		private Object parent;
	}

	private static PathNode analyzePosition(String position) {
		if (StringUtils.isBlank(position)) {
			throw new RuntimeException("unsupport blank position");
		}
		return pathNodeCache.computeIfAbsent(position, p -> doAnalyzePosition(p));
	}

	// 支持 [1].a.b
	// 不支持 a.[1].b
	// 返回头节点，头节点无数据
	// 使用list会导致遍历时生成大量subList
	private static PathNode doAnalyzePosition(String position) {
		PathNode head = PathNode.builder().isHead(true).build();
		PathNode cur = head;
		String[] parts = StringUtils.split(position, '.');
		for (String p : parts) {
			if (StringUtils.endsWith(p, "]")) {
				String[] indexArray = StringUtils.split(p, '[');
				for (String a : indexArray) {
					if (StringUtils.endsWith(a, "]")) { // [1][2].a.b
						String value = StringUtils.removeEnd(a, "]");
						if ("*".equals(value)) {
							cur = cur.add(PathNode.builder().type(NodeType.STAR).build());
						} else {
							cur = cur.add(PathNode.builder().type(NodeType.INDEX).index(Integer.parseInt(value)).build());
						}
					} else {
						// map.values()[*]
						if (StringUtils.endsWith(a, "()")) {
							cur = cur.add(PathNode.builder().type(NodeType.FUNC).field(a).build());
						} else { // a[1][2].b.c
							cur = cur.add(PathNode.builder().type(NodeType.FIELD).field(a).build());
						}
					}
				}

			} else if (StringUtils.endsWith(p, "()")) {
				cur = cur.add(PathNode.builder().type(NodeType.FUNC).field(p).build());
			} else {
				cur = cur.add(PathNode.builder().type(NodeType.FIELD).field(p).build());
			}
		}
		return head;
	}

	// 中间对象非map的话，则不支持replace，会不起作用
	public static void replace(Object obj, String position, Function<Object, Object> replaceFunc) {
		Cursor cursor = createCursor(obj, position);
		traverse(cursor, TraverseConfig.builder().isConvertConstruct(false).build(), cur -> { // 只有找到才会进入，所以target不会为空
			Object newValue = replaceFunc.apply(cur.target);
			if (cur.node.type == NodeType.FIELD) {
				Map parentMap = toMap(cur.parent);
				parentMap.put(cur.node.field, newValue);
			} else if (cur.node.type == NodeType.STAR || cur.node.type == NodeType.INDEX) {
				List parentList = toList(obj);
				parentList.set(cur.node.index, newValue);
			} else {
				throw new RuntimeException("unsupport node type [" + cur.node.type.name() + "] in replace operation.");
			}
		});
	}

	// 不支持null，如果入参为null的话，生成对象内存地址跟预期的不一致
	public static void put(Object obj, String position, Object value) {
		if (obj != null && (obj instanceof Map || obj instanceof List)) {
			Cursor cursor = createCursor(obj, position);
			traverse(cursor, TraverseConfig.builder().isConvertConstruct(false) // 不改变结构，改变结构，写入会失效
					.isForceEnter(true).fillListValue(null).build(), cur -> { // 强制进入，target可能为null
						putToThisCur(cur, value);
					});
		} else {
			throw new RuntimeException("only support map and list type");
		}
	}

	// 挂载到父节点
	private static void putToThisCur(Cursor cur, Object obj) {
		if (cur.parent instanceof Map) {
			Map parentMap = toMap(cur.parent);
			parentMap.put(cur.node.field, obj);
		} else if (cur.parent instanceof List) {
			List parentList = toList(obj);

			if (cur.node.type == NodeType.INDEX) {
				// index越界时，添加到list最后一位
				if (cur.node.index < parentList.size()) {
					parentList.set(cur.node.index, obj);
				} else {
					parentList.add(obj);
				}
			} else if (cur.node.type == NodeType.STAR) { // *号就全set
				for (int i = 0; i < parentList.size(); i++) {
					parentList.set(i, obj);
				}
			} else {
				throw new RuntimeException("unsupport node type [" + cur.node.type.name() + "] in put operation.");
			}
		}
	}

	@Data
	@Builder
	private static class TraverseConfig {
		private boolean isConvertConstruct;
		private boolean isForceEnter;
		private Object fillListValue;
	}

	// a[1].b[*].c
	// a[*].b[*].c
	// [1].b.c
	// 仅支持*字符
	public static List<Object> pickList(Object obj, String position) {
		List<Object> list = Lists.newArrayList();
		Cursor cursor = createCursor(obj, position);
		traverse(cursor, TraverseConfig.builder().isConvertConstruct(true).build(), cur -> {
			list.add(cur.target);
		});
		return list;
	}

	private static Cursor createCursor(Object obj, String position) {
		PathNode head = analyzePosition(position);
		Cursor cursor = new Cursor();
		cursor.node = head;
		cursor.target = obj;
		return cursor;
	}

	// 只返回单个object
	public static Object pickOne(Object obj, String position) {
		List<Object> list = pickList(obj, position);
		return CollectionUtils.isEmpty(list) ? null : list.get(0);
	}

	// 可能返回list，也可能返回单个object
	public static Object pick(Object obj, String position) {
		if (isPickDirect(position)) {
			return pickField(obj, position);
		}

		if (isPickList(position)) {
			return pickList(obj, position);
		}
		return pickOne(obj, position);
	}

	public static String pickString(Object obj, String position) {
		return UnifyConverter.toString(pick(obj, position));
	}

	public static Object pickField(Object obj, String field) {
		try {
			return PropertyUtils.getProperty(obj, field);
		} catch (Exception e) {
			return null;
		}
	}

	// 不生成新对象（map）
	private static Map<?, ?> toMap(Object obj) {
		if (obj == null) {
			return null;
		}
		if (obj instanceof Map) {
			return (Map<?, ?>) obj;
		}
		return null;
	}

	// 不生成新对象
	private static List<?> toList(Object obj) {
		if (obj == null) {
			return null;
		}
		if (obj instanceof List) {
			return (List<?>) obj;
		}
		return null;
	}

	private static Object createNextBlankObject(Cursor cur) {
		if (cur.node.hasNext()) {
			NodeType nextType = cur.node.next.type;
			if (nextType == NodeType.FIELD) {
				return UnifyMap.of();
			} else if (nextType == NodeType.INDEX || nextType == NodeType.STAR) {
				return Lists.newArrayList();
			} else {
				throw new RuntimeException("unsupport node type [" + cur.node.type.name() + "] in create operation.");
			}
		}

		return null;
	}

	private static void setToList(List list, int index, Object value, Object fillBlankValue) {
		if (index < list.size()) {
			list.set(index, value);
		} else {
			for (int i = list.size(); i <= index; i++) {
				if (i == index) {
					list.add(value);
				} else {
					list.add(fillBlankValue);
				}
			}
		}
	}

	// 单线程遍历
	private static void traverse(Cursor cur, TraverseConfig config, Consumer<Cursor> consumer) {
		while (cur.node.hasNext()) {
			cur.node = cur.node.next();
			NodeType type = cur.node.getType();

			if (type == NodeType.FIELD) {
				Map parentMap;
				if (config.isConvertConstruct()) {
					parentMap = UnifyConverter.toUnifyMap(cur.target);
				} else {
					parentMap = toMap(cur.target);
				}

				if (parentMap == null) {
					break; // 中断遍历，没有找到
				}

				Object tempTarget = parentMap.get(cur.node.field);
				if (tempTarget == null && config.isForceEnter) {
					tempTarget = createNextBlankObject(cur);
					// 需要把节点挂到父节点上
					parentMap.put(cur.node.field, tempTarget);
				}
				cur.parent = cur.target;
				cur.target = tempTarget;
				if (!cur.node.hasNext()) {
					if (cur.target != null || config.isForceEnter) {
						consumer.accept(cur);
					}
				}
			} else if (type == NodeType.FUNC) { // 只支持读取，不支持写入
				Object tempTarget = null;
				switch (cur.node.field) {
				case "keys()":
					tempTarget = handleKeysFunc(cur.target);
				case "values()":
					tempTarget = handleValuesFunc(cur.target);
				default:
				}
				cur.parent = cur.target;
				cur.target = tempTarget;
				if (!cur.node.hasNext() && cur.target != null) { // 找到才填充
					consumer.accept(cur);
				}
			} else {
				List tempList;
				if (config.isConvertConstruct()) {
					tempList = UnifyConverter.toList(cur.target, Object.class);
				} else {
					tempList = toList(cur.target);
				}

				if (tempList == null) {
					break; // 中断遍历，没有找到
				}

				if (type == NodeType.STAR) {
					PathNode loopNode = cur.node;
					for (int i = 0; i < tempList.size(); i++) {
						Object o = tempList.get(i);
						cur.parent = cur.target;
						cur.target = o;
						// 进入迭代之后，node会变化，等出来时，node已经到达最后一位了，需要保存进入迭代之前的node状态
						cur.node = loopNode;
						cur.node.index = i;
						traverse(cur, config, consumer);
						cur.node.index = 0; // 星号node index会传入递归，之后清零
					}
					break; // 交给递归去查询node后，外层不能再继续执行node查询了
				} else if (type == NodeType.INDEX) {
					Object tempTarget = cur.node.index < tempList.size() ? tempList.get(cur.node.index) : null;
					if (tempTarget == null && config.isForceEnter) {
						tempTarget = createNextBlankObject(cur);
						// 需要把节点挂到父节点上
						setToList(tempList, cur.node.index, tempTarget, config.getFillListValue());
					}

					cur.parent = cur.target;
					cur.target = cur.node.index < tempList.size() ? tempList.get(cur.node.index) : null;
					if (!cur.node.hasNext()) {
						if (cur.target != null || config.isForceEnter) {
							consumer.accept(cur);
						}
					}
				}
			}
		}
	}

	private static Set handleKeysFunc(Object target) {
		Map parentMap = UnifyConverter.toUnifyMap(target);
		return parentMap == null ? null : parentMap.keySet();
	}

	private static Collection handleValuesFunc(Object target) {
		Map parentMap = UnifyConverter.toUnifyMap(target);
		return parentMap == null ? null : parentMap.values();
	}

	public static boolean isPickDirect(String path) {
		return !path.contains("]") && !path.contains(".");
	}

	public static boolean isPickList(String path) {
		return path.contains("*");
	}
}